package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.PointsRecord;
import com.tianji.learning.domain.po.SignRecord;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.enums.PointsRecordType;
import com.tianji.learning.mapper.SignRecordMapper;
import com.tianji.learning.mq.message.SigninMessage;
import com.tianji.learning.service.IPointsRecordService;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

import static com.tianji.common.constants.MqConstants.Exchange.LEARNING_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.SIGN_IN;

/**
 * <p>
 * 签到记录表 服务实现类
 * </p>
 *
 * @author xzh
 * @since 2024-03-18
 */
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class SignRecordServiceImpl extends ServiceImpl<SignRecordMapper, SignRecord> implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper mqHelper;
    private final IPointsRecordService recordService;

    /**
     * 签到功能接口
     *
     * @return
     */
    @Override
    public SignResultVO addSignRecords() {
        //1.签到
        //1.1获取当前登录用户id
        Long userId = UserContext.getUser();
        //1.2获取日期
        LocalDate now = LocalDate.now();
        //1.3拼接key
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX
                + userId
                + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        //1.4计算offset 角标 索引当前天数-1
        int offset = now.getDayOfMonth() - 1;
        //1.5保存签到信息  setBit:true代表1 flase代表0  他会将vaule添加然后返回这个位置原本的值 默认不添加就是flase
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(exists)) {
            throw new BizIllegalException("不允许重复签到");
        }
        //2.计算连续签到天数
        int signDays = countSignDays(key, now.getDayOfMonth());
        //3.计算得分
        int rewardPoints = 0;
        switch (signDays) {
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 40;
                break;
        }
        //4.发送mq异步请求
        mqHelper.send(LEARNING_EXCHANGE, SIGN_IN,
                SigninMessage.of(userId, rewardPoints + 1));//+1是每天签到的基本积分
        //5.封装 返回
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);
        return vo;
    }

    /**
     * 添加 签到 回答问题 每学习一小节 积分记录
     *
     * @param userId
     * @param points
     * @param type
     */
    @Override
    public void addPointsRecord(Long userId, Integer points, PointsRecordType type) {
        PointsRecord record = new PointsRecord();
        if (PointsRecordType.SIGN.equals(type)) {
            // 登录积分 没有上限 一天一次 直接在数据库增加
            int recordType = PointsRecordType.SIGN.getValue();
            //新增数据库
            savePointsRecord(userId, points, record, recordType);
        } else if (PointsRecordType.QA.equals(type)) {
            // todo 可能出错 没有先建立redis库就直接获取里面的值

            String key = RedisConstants.WRITE_POINTS_KEY;
            String hashKey = RedisConstants.POINTS_RECORD_KEY_PREFIX + userId;
            // 防止出错先建立redis库
            redisTemplate.opsForHash().put(key, "test", 0);
            Integer o = (Integer) redisTemplate.opsForHash().get(key, hashKey);
            if (o == null || o < 20) {
                redisTemplate.opsForHash().put(key, hashKey, points);
                //新增数据库
                int recordType = PointsRecordType.QA.getValue();
                savePointsRecord(userId, points, record, recordType);

            }
        } else {
            String key = RedisConstants.LEARN_POINTS_KEY;
            String hashKey = RedisConstants.POINTS_RECORD_KEY_PREFIX + userId;
            // 防止出错先建立redis库
            redisTemplate.opsForHash().put(key, "test", 0);
            Integer o = (Integer) redisTemplate.opsForHash().get(key, hashKey);
            if (o == null || o > 50) {
                redisTemplate.opsForHash().put(key, hashKey, points);
                //新增数据库
                int recordType = PointsRecordType.LEARNING.getValue();
                savePointsRecord(userId, points, record, recordType);
            }
        }

    }

    /**
     * 获得每天的登录情况
     *
     * @return
     */
    @Override
    public List<Long> getSignRecords() {
        Long userId = UserContext.getUser();
        int today = LocalDateTime.now().getDayOfMonth();
        List<Long> list = redisTemplate.opsForValue()
                // todo SIGN_RECORD_KEY_PREFIX + userId,后面没加时间 key错误 取不出值来
                .bitField(RedisConstants.SIGN_RECORD_KEY_PREFIX + userId,
                        BitFieldSubCommands.create()
                                .get(BitFieldSubCommands.BitFieldType.unsigned(today))
                                .valueAt(0));
        if (CollUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        return list;
    }

    private void savePointsRecord(Long userId, Integer points, PointsRecord record, int recordType) {
        record.setCreateTime(LocalDateTime.now());
        record.setPoints(points);
        record.setType(recordType);
        record.setUserId(userId);
        recordService.savePointsRecord(record);
    }


    /**
     * 计算连续签到天数
     *
     * @param key
     * @param len
     * @return
     */
    private int countSignDays(String key, int len) {
        //1.获取从本月开始第一天到所有天数的记录
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(len)).valueAt(0)
                );
        //健壮性判断
        if (CollUtils.isEmpty(result)) {
            return 0;
        }
        //2.创建成员变量 count
        int count = 0;
        //2.1遍历
        for (int i = 0; i < result.size(); i++) {
            if (result.get(result.size() - i - 1) == 1L) {
                count++;
            } else {
                break;
            }
        }
        //3.返回
        return count;
    }
}
